﻿using System;
using System.Collections.Generic;
using System.IO;
using EmbeddedSharp.Core;
using EmbeddedSharp.Core.Function.Arithmetics;
using EmbeddedSharp.Core.Function.Trigonometry;
using EmbeddedSharp.Core.TextConentProviders;
using EmbeddedSharp.Scripting;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Zove.Runtime.Core;
using Zove.Runtime.Core.EquationTransformer;
using Zove.Runtime.Core.Expression;
using Zove.Runtime.Core.InferencingStrategy;
using Zove.Runtime.Core.Zoom;
using Zove.Runtime.Queries;
using Zove.Scripting.Core;

namespace Zove.Runtime.Test {
    [TestClass]
    public class RuntimeTests {
        public static void ShowSolution(ZoveObject sln) {
            foreach (RuntimeFactor rf in sln.Model.Factors) {
                var ss = new SolutionFormulator();
                string expr = ss.Formulate(rf);
                Console.WriteLine(string.Format(
                    "{0} = {1}:{2}",
                    rf.Name, rf.Value, expr));
                //sln.Queue.ContainsKey(e.Name)
                //? sln.Queue[e.Name].Rule.Root.ToString() : "input")));
            }
        }

        [TestMethod]
        public void TestRun() {
            string fileName =
                @"C:\Program Files\Zove\Models\Applications\Geometry\Optimization\Solid\Sphere\Cone\SphereCone.fpz";
            string source = File.ReadAllText(fileName);

            var intp = new ZoveScriptingObject(source);
            intp.Run();

            Console.WriteLine(intp.Context.GetVariable("sc.volume"));
        }

        [TestMethod]
        public void TestEquationInferencing() {
            ZoveObject instance =
                ZoveObject.Allocate(
                    new StringContentProvider(@"class Test {
factor a, b, c;

a = b/c*3;
b = c*a/2;
c = b/a;
}"),
                    new ZoomLanguageNature());

            instance.Initialize("a", "2");

            instance.InferenceStrategies.Remove("network");
            instance.InferenceStrategies.Remove("equation");

            Console.WriteLine(
                instance.Model.Factors["b"].Value);
            Console.WriteLine(
                instance.Model.Factors["c"].Value);
        }

        /// <summary>
        /// Assert a factor is solved and the result is expected
        /// </summary>
        /// <param name="rf"></param>
        /// <param name="value"></param>
        public void AssertFactor(RuntimeFactor rf, double value) {
            Assert.IsTrue(rf.Status == RuntimeFactorStatus.Input
                          || rf.Status == RuntimeFactorStatus.Solved, "The factor is not yet solved");
            Assert.AreEqual(rf.Value, value);
        }

        [TestMethod]
        public void TestDebugging() {
            var bp = new BreakPoint(2);
            var intp = new ZoveScriptingObject(@"hello=1;hello=3;hello=7;");
            intp.BreakPoints.Add(bp);
            intp.IsDebugging = true;
            intp.Run();

            var var = (string)intp.Context.GetVariable("hello");
            Assert.AreEqual(1, Convert.ToDouble(var));

            intp.Resume();

            var = (string)intp.Context.GetVariable("hello");
            Assert.AreEqual(7, Convert.ToDouble(var));
        }

        [TestMethod]
        public void TestEnquiry() {
            var queries = new[]
                              {
                                  "select * from circle where r=10;",
                                  "select volume from spherecone where r=10, alpha=2.13213;",
                                  "select * from spherecone where t.A=3.14/3, t.b=22;",
                                  "select * from righttriangle where A=3.14/3, b=22;"
                              };

            var qm = new QueryManager(new ZoomLanguageNature());
            foreach (string q in queries) {
                Query query = qm.CreateQuery(q);
                Dictionary<string, QueryResult> results = query.Run();

                foreach (string key in results.Keys) {
                    Console.WriteLine(results[key].Value);
                }
            }
        }

        [TestMethod]
        public void TestRuntimeModelLoader() {
            var finder = new ZoomClassFinder();
            var classLoad = new DefaultClassLoader(new ZoomLanguageNature());
            classLoad.ClassFinder = finder;
            var matLoad = new DefaultRuntimeModelLoader(new ZoomLanguageNature());
            matLoad.ClassLoader = classLoad;
            RuntimeModel rm = matLoad.LoadByClassName("circle");

            Console.WriteLine(rm.ToString());
        }

        [TestMethod]
        public void TestClassLoad() {
            var finder = new ZoomClassFinder();
            var load = new DefaultClassLoader(new ZoomLanguageNature());
            load.ClassFinder = finder;
            ZoomClass c = load.LoadByClassName("cube");

            Console.WriteLine(c.ToString());

            Assert.AreEqual(3, c.Variables.Count);
            Assert.AreEqual(2, c.Equations.Count);
            ZoomEquation e = c.Equations[0];
            Assert.AreEqual(2, e.Elements.Count);
        }

        [TestMethod]
        public void TestBasic() {
            var languageNature = new ZoomLanguageNature();
            var contentProvider = new ClassContentProvider("circle", languageNature);
            var instance = new ZoveObject("", contentProvider, languageNature);

            instance.Where("r=1").Assert("d=2", "area=3.1415926");
        }

        public ZoveObject TestModel(
            string className, string source, Dictionary<string, double> data) {
            ZoveObject instance = SetupInstance(className);
            instance.Model = instance.Loader.LoadBySource(source);

            return instance;
        }

        private static void InitializeParameters(ZoveObject instance, Dictionary<string, double> data) {
            foreach (string m in data.Keys) {
                RuntimeFactor rf = instance.Model.Factors[m];
                if (rf == null) {
                    throw new FactorNotDeclaredException(
                        m, "", instance.Model.Name);
                }

                rf.Initialize(data[m]);
            }
        }

        private ZoveObject AllocateModel(
            string className, Dictionary<string, double> data) {
            ZoveObject instance = SetupInstance(className);
            //rml.Logger = new DefaultLogger();
            instance.Model = instance.Loader.LoadByClassName(className);

            InitializeParameters(instance, data);
            // instance.Evaluate();
            //ShowSolution(instance);

            return instance;
        }

        private static ZoveObject SetupInstance(string className) {
            var languageNature = new ZoomLanguageNature();
            var contentProvider = new ClassContentProvider(className, languageNature);
            var sln = new ZoveObject("", contentProvider, languageNature);

            IClassFinder finder = languageNature.ClassFinder;
            IClassLoader cl = languageNature.ClassLoader;

            string myDocumentsLoc = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
            string visualStudioProjectLoc = myDocumentsLoc +
                                            @"\Visual Studio 2008\Projects\Zove\Zove.Runtime.Test\Files\";
            finder.AddClassPath(visualStudioProjectLoc);
            var rml = new DefaultRuntimeModelLoader(new ZoomLanguageNature()) {ClassLoader = cl};
            sln.Loader = rml;

            return sln;
        }

        protected void DisplayUnsolvedFactors(ZoveObject sln) {
            foreach (RuntimeFactor rf in sln.Model.Factors) {
                if (rf.Status == RuntimeFactorStatus.Unknown) {
                    Console.WriteLine(
                        string.Format("{0}={1}:{2}", rf.Name,
                                      rf.Value,
                                      (rf.Status == RuntimeFactorStatus.Input
                                           ? "<input>"
                                           : (rf.Status == RuntimeFactorStatus.Solved ? "<solved>" : "<unknown>")
                                      ))
                        );
                }
            }
        }

        [TestMethod]
        public void TestSphere() {
            var languageNature = new ZoomLanguageNature();
            var zoveObject = new ZoveObject(
                string.Empty, 
                new ClassContentProvider("sphere", languageNature),
                languageNature);

            // , "a=1", "b=1", "c=1"
            zoveObject.Where("r=1").Assert("volume=4.18879013333333");
        }

        [TestMethod]
        public void TestCircle() {
            ZoomLanguageNature languageNature = new ZoomLanguageNature();
            ZoveObject zoveObject = new ZoveObject(
                string.Empty,
                new ClassContentProvider("circle", languageNature),
                languageNature);

            zoveObject.Where("r=1").Assert("area=3.1415926");
        }

        [TestMethod]
        public void TestInheritance() {
            var i
                = new Dictionary<string, double>();
            i["edge"] = 2;

            ZoveObject zoveObject = AllocateModel("square", i);
            zoveObject.Where("edge=2").Assert("width=2", "height=2", "area=2");
        }

        [TestMethod]
        public void TestParse() {
            var p = new Expression("a - b*c");
            p.ExpressionTree.PrintTree();

            var eci = new DefaultExpressionTranslator();
            string expr = p.Translate(eci);

            Assert.AreEqual("(a-(b*c))", expr);
        }

        [TestMethod]
        public void TestEmbrace() {
            var p = new ExpressionParser("abs(1-3)");
            p.ConstantManager
                = DefaultDependencyManager.Instance.ConstantManager;
            p.FunctionManager
                = DefaultDependencyManager.Instance.FunctionManager;
            Node root = p.Parse();
            root.PrintTree();
            var eci = new DefaultExpressionTranslator();
            Console.WriteLine(eci.Translate(root));

            string result = root.Evaluate().ToString();
            Console.WriteLine(result);
        }

        [TestMethod]
        public void TestPrintTree() {
            Node root = GetSampleTree();

            root.PrintTree();
        }

        public static Node GetSampleTree() {
            var root = new AddNode();

            var left = new FactorNode { Factor = new RuntimeFactor { Name = "a", Value = 5 }, Value = "a" };

            var right = new FactorNode { Value = "b", Factor = new RuntimeFactor { Value = 6, Name = "c" } };

            root.Left = left;
            root.Right = right;

            return root;
        }

        [TestMethod]
        public void TestRouteFinder() {
            var p = new ExpressionParser("2*r^2")
                        {
                            ConstantManager = DefaultDependencyManager.Instance.ConstantManager,
                            FunctionManager = DefaultDependencyManager.Instance.FunctionManager
                        };
            Node root = p.Parse();

            var finder = new RouteFinder("r");
            bool result = finder.Visit(root);

            Assert.AreEqual(true, result);
            Assert.AreEqual(1, finder.Route.Pop());
            Assert.AreEqual(0, finder.Route.Pop());
        }

        [TestMethod]
        public void TestCompileExpr() {
            var p = new ExpressionParser("sin(a+b)")
                        {
                            ConstantManager = DefaultDependencyManager.Instance.ConstantManager,
                            FunctionManager = DefaultDependencyManager.Instance.FunctionManager
                        };
            Node root = p.Parse();

            var impl
                = new DefaultExpressionTranslator();
            Assert.AreEqual("sin((a+b))", impl.Translate(root));
        }

        [TestMethod]
        public void TestVisit() {
            RuntimeModel m = GetRuntimeModel();

            Node node = GetSampleTree();
            double result = Convert.ToDouble(node.Evaluate());
            Assert.AreEqual(11, result);
        }

        [TestMethod]
        public void TestEvaluate() {
            RuntimeModel m = GetRuntimeModel();

            var ze = new ZoomExpression("((b/3.14)^0.5)", m);
            double result = Convert.ToDouble(ze.Evaluate());

            Assert.AreEqual(Math.Round(1.3823270327522743, 5), Math.Round(result, 5));
        }

        [TestMethod]
        public void TestParseFunction() {
            var parser = new ExpressionParser("a+sin(b)")
                             {
                                 ConstantManager = DefaultDependencyManager.Instance.ConstantManager,
                                 FunctionManager = DefaultDependencyManager.Instance.FunctionManager
                             };
            Node root = parser.Parse();

            root.PrintTree();
        }

        [TestMethod]
        public void TestCheckDuplicated() {
            const string equ = "b = (a^2+c^2-2*a*c*cos(B))'2";
            List<string> dup = DefaultEquationTransformer.CheckDuplicated(equ);

            Assert.AreEqual(2, dup.Count);
            Assert.AreEqual("a", dup[0]);
            Assert.AreEqual("c", dup[1]);
        }

        [TestMethod]
        public void TestTransformAll() {
            string equ = "b = (a^2+c^2-2*a*c*cos(B))'2";
            RuntimeModel rm = GetRuntimeModel("a", "b", "c", "B");

            DefaultEquationTransformer eti = null;
            //    = new EquationTransformerImpl(equ, rm);
            Dictionary<string, string> e = null; // eti.Transform();

            //Assert.AreEqual(2, e.Count);

            equ = "a+b=c-d";
            rm = GetRuntimeModel("a", "b", "c", "d");
            eti = new DefaultEquationTransformer(equ, rm, new ZoomLanguageNature());
            e = eti.Transform();

            Assert.AreEqual(4, e.Count);
            Assert.AreEqual("((c-d)-a)", e["b"]);
            Assert.AreEqual("((c-d)-b)", e["a"]);
        }

        [TestMethod]
        public void TestGetInstance() {
            object instance = Utils.GetObject(
                @"C:\windows\assembly\System.dll", "System.Byte");

            Assert.IsNotNull(instance);
            Assert.IsTrue(instance is Byte);
        }

        [TestMethod]
        public void TestFind() {
            var finder = new ZoomClassFinder();
            Assert.AreEqual(
                finder.GetLocation("cone"),
                finder.GetLocation("cone"));
        }

        [TestMethod]
        public void TestAutomatedModelingBuilding() {
        }

        [TestMethod]
        public void TestTypeChecking() {
            try {
                ZoveObject sln = ZoveObject.Allocate(new StringContentProvider(
                                                         @"class Test { factor a; a = b+1;}"),
                                                     new ZoomLanguageNature());
                Assert.IsTrue(false, "element undelcared was not checked.");
            } catch (FactorNotDeclaredException ex) {
                Console.WriteLine(ex.Message);
                Assert.IsTrue(true);
            }
        }

        [TestMethod]
        public void TestFill() {
            string r = string.Empty;
            CSharpExpressionTranslator ef;

            //ef = new ExpressionFiller("a+b", rm);
            //string r = ef.Fill();
            //Assert.AreEqual("(x+6)", r);
            var a = new FactorNode { Value = "a", Factor = new RuntimeFactor { Name = "a" } };
            a.Factor.Reset();

            var b = new FactorNode { Value = "b", Factor = new RuntimeFactor { Name = "b" } };
            b.Factor.Initialize(6);

            var s = new SinNode();
            s.Parameters.Add(a);
            s.Parameters.Add(b);

            ef = new CSharpExpressionTranslator();
            r = ef.Translate(s);

            Assert.AreEqual("Math.Sin(x,6)", r);
        }

        public static RuntimeModel GetRuntimeModel() {
            var rm = new RuntimeModel();
            var rf = new RuntimeFactor { Name = "a" };
            rf.Reset();
            rm.Factors.Add(rf);

            rf = new RuntimeFactor { Name = "b" };
            rf.Initialize(6);
            rm.Factors.Add(rf);

            rf = new RuntimeFactor { Name = "c" };
            rf.Initialize(6);
            rm.Factors.Add(rf);

            rf = new RuntimeFactor { Name = "B" };
            rf.Initialize(6);
            rm.Factors.Add(rf);

            return rm;
        }

        [TestMethod]
        public void TestFindRoot() {
            var eis = new EquationInferencingStrategy(new ZoomLanguageNature());
            double result = eis.Evaluate("x-2");

            Console.WriteLine(result);
        }

        public static RuntimeModel GetRuntimeModel(
            params string[] factors) {
            var rm = new RuntimeModel();

            foreach (string factor in factors) {
                var rf = new RuntimeFactor(factor);

                rm.Factors.Add(rf);
            }

            return rm;
        }

        [TestMethod]
        public void TestGetFunctionName() {
            Assert.AreEqual(
                "Math.Sin",
                CSharpExpressionTranslator.GetFunctionName("sin"));
        }

        [TestMethod]
        public void TestNonlinearEquationFinding() {
            var i
                = new Dictionary<string, double>();
            i["a"] = 1;

            ZoveObject instance =
                ZoveObject.Allocate(
                    new StringContentProvider(@"class Test {
factor a, b, c;
a = b + c;
b = a*2/c;
c = b*2-a;
}"),
                    new ZoomLanguageNature());

            instance.Initialize("a", "1");

            AssertFactor(instance.Model.Factors["b"], 5);
            AssertFactor(instance.Model.Factors["c"], 5);
        }

        [TestMethod]
        public void TestComment() {
            var i = new Dictionary<string, double>();

            AllocateModel("CommentTest", i);
        }

        [TestMethod]
        public void TestSimplify() {
            var i
                = new Dictionary<string, double>();
            i["c.area"] = 2;

            ZoveObject sln = AllocateModel("sphere", i);
            RuntimeFactor rf = sln.Model.Factors["volume"];

            var ss = new SolutionFormulator();
            string result = ss.Formulate(rf);

            Console.WriteLine("result: {0}", result);
        }

        [TestMethod]
        public void TestFunctionEvaluation() {
            var i = new Dictionary<string, double>();
            i["a"] = 3;

            ZoveObject instance = AllocateModel("FuncTest", i);

            AssertFactor(instance.Model.Factors["b"], 1);
        }

        [TestMethod]
        public void TestPreprocess() {
            var languageNature = new ZoomLanguageNature();
            ZoomClass zc = languageNature.ClassLoader.LoadByClassName("triangle");

            RuntimeModel rm = languageNature.ModelLoader.LoadByClassName("Triangle");
            string expr = "Triangle.sine(b.b,b.A,b.B)";
            var converter = new UserDefinedFunctionConverter();

            var rml = new DefaultRuntimeModelLoader(new ZoomLanguageNature());
            string newe = rml.PreprocessExpr(
                zc, rm, converter, expr);
        }

        [TestMethod]
        public void TestRule() {
            var languageNature = new ZoomLanguageNature();
            ZoomClass c = languageNature.ClassLoader.LoadFromSource(
                @"class A {
                    facotr a, b;

                    g(x)->x/3
                    a = g(b);
                }");

            Console.WriteLine(c.ToString());
        }

        [TestMethod]
        public void TestRadianClass() {
            var languageNature = new ZoomLanguageNature();
            var contentProvider = new ClassContentProvider("RadianClass", languageNature);
            var instance = new ZoveObject("", contentProvider, languageNature);

            var rml = new DefaultRuntimeModelLoader(new ZoomLanguageNature());
            instance.Model = rml.LoadByClassName("RadianClass");

            instance.Initialize("angle", "30 \\angle");
            instance.Evaluate();

            ShowSolution(instance);
        }

        [TestMethod]
        public void TestImportWorkspace() {
            var setup = new Dictionary<string, double>();
            setup.Add("hello.a", 5);

            ZoveObject context = AllocateModel("ImportTest", setup);
        }

        [TestMethod]
        public void TestFormulate() {
            string source = "triangle t;t.A=1.312;t.b=2,t.c=3;d=formulate(t.a, t.A);";
            var script = new ZoveScriptingObject(source);
            script.Run();

            IContext cntx = script.Context;
            object var = cntx.GetVariable("d");
        }
    }
}